This innovative project lets you compose music using gestures. You will create a hands-free digital instrument that relies on an ultrasonic sensor to measure hand distance. As the distance changes, the system plays a full eight-note musical scale on a connected buzzer. Concurrently, a unique, dynamic visual pattern corresponding to each note is displayed on an 8×8 LED Matrix. It’s an ideal way to explore the interface between physical movement, audio output, and digital graphics.
Key Concepts
- Ultrasonic Sensing (HC-SR04): Measuring distance using sound waves.
- Signal Mapping: Converting a physical distance range (5cm to 40cm) into a discrete musical scale (8 notes).
- Tone Generation: Using the ESP32’s
tone()function on a buzzer to produce musical frequencies. - SPI Display: Controlling the MAX7219 LED Matrix to display graphics.
- I2C Display: Providing clear, real-time feedback on the note and distance via a 16×2 LCD.
Circuit Diagram
This map shows you the physical map for building the project. It shows exactly where to plug in every wire for the components so that the software instructions can correctly control all the hardware pieces.

| Components | ESP32 Dev Module |
| Ultrasonic (TRIG/ECHO) | GPIO5/GPIO18 |
| Buzzer | GPIO25 |
| LCD 16×2 I2C (SDA/SCL) | SDA/SCL |
| Dot Matrix (DIN/CS/CLK) | GPIO23/GPIO15/GPIO19 |
Code Lab
This guide is your fast-fix manual for when errors occur. It helps you quickly diagnose why something isn’t working, like a dark screen or a silent buzzer—and provides simple steps to correct the problem.
Step 1: Install Libraries
Before uploading the code, you must install these two external libraries in your Arduino IDE via the Library Manager
- LiquidCrystal_I2C.h by Frank de Brabander
- MD_MAX72XX.h by MajicDesings
For example:

Step 2: Full Code
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <MD_MAX72xx.h>
// Pin Definitions
#define TRIG_PIN 5
#define ECHO_PIN 18
#define BUZZER_PIN 25
// 8x8 LED Matrix pins (DIN, CLK, CS)
#define DIN_PIN 23
#define CLK_PIN 19
#define CS_PIN 15
// LCD I2C address (common: 0x27 or 0x3F)
LiquidCrystal_I2C lcd(0x27, 16, 2);
// LED Matrix - Use FC16 hardware type for generic MAX7219 modules
MD_MAX72XX mx = MD_MAX72XX(MD_MAX72XX::FC16_HW, DIN_PIN, CLK_PIN, CS_PIN, 1);
// Piano notes (frequencies in Hz)
const int notes[] = {262, 294, 330, 349, 392, 440, 494, 523}; // C4 to C5
const char* noteNames[] = {"C", "D", "E", "F", "G", "A", "B", "C5"};
// Distance ranges for each note (in cm)
const int minDistance = 5;
const int maxDistance = 40;
// Variables to track current note
int currentNote = -1;
unsigned long lastUpdateTime = 0;
const int UPDATE_INTERVAL = 50; // Update display every 50ms
// LED patterns for each note (8x8 matrix)
uint8_t notePatterns[8][8] = {
{0x18, 0x24, 0x42, 0x81, 0x81, 0x42, 0x24, 0x18}, // Note 0 - Circle
{0xFF, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0xFF}, // Note 1 - Square
{0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18}, // Note 2 - Line
{0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55}, // Note 3 - Checker
{0x81, 0x42, 0x24, 0x18, 0x18, 0x24, 0x42, 0x81}, // Note 4 - X
{0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}, // Note 5 - Stripes
{0x3C, 0x42, 0x81, 0x81, 0x81, 0x81, 0x42, 0x3C}, // Note 6 - Big Circle
{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF} // Note 7 - Full
};
void setup() {
Serial.begin(115200);
// Initialize LCD
lcd.init();
lcd.backlight();
lcd.setCursor(0, 0);
lcd.print("Ultrasonic Piano");
lcd.setCursor(0, 1);
lcd.print("Initializing...");
// Initialize LED Matrix
mx.begin();
mx.clear();
mx.control(MD_MAX72XX::INTENSITY, 8); // Brightness 0-15
// Initialize pins
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(BUZZER_PIN, OUTPUT);
delay(2000);
lcd.clear();
}
void loop() {
// Measure distance
long distance = getDistance();
// Map distance to note index (0-7)
if (distance >= minDistance && distance <= maxDistance) {
int noteIndex = map(distance, minDistance, maxDistance, 0, 7);
noteIndex = constrain(noteIndex, 0, 7);
// Only play new note if it changed
if (noteIndex != currentNote) {
currentNote = noteIndex;
playNote(noteIndex);
displayPattern(noteIndex);
}
// Update display periodically
if (millis() - lastUpdateTime > UPDATE_INTERVAL) {
displayNote(noteIndex, distance);
lastUpdateTime = millis();
}
} else {
// No valid distance, stop sound
if (currentNote != -1) {
noTone(BUZZER_PIN);
currentNote = -1;
mx.clear();
}
// Update display periodically
if (millis() - lastUpdateTime > UPDATE_INTERVAL) {
lcd.setCursor(0, 0);
lcd.print("Move hand closer");
lcd.setCursor(0, 1);
lcd.print("5-40cm range ");
lastUpdateTime = millis();
}
}
delay(10); // Small delay for stability
}
long getDistance() {
// Trigger ultrasonic pulse
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
// Read echo pulse
long duration = pulseIn(ECHO_PIN, HIGH, 30000);
// Calculate distance in cm
long distance = duration * 0.034 / 2;
return distance;
}
void playNote(int noteIndex) {
tone(BUZZER_PIN, notes[noteIndex]);
}
void displayNote(int noteIndex, long distance) {
lcd.setCursor(0, 0);
lcd.print("Note: ");
lcd.print(noteNames[noteIndex]);
lcd.print(" ");
lcd.print(notes[noteIndex]);
lcd.print("Hz ");
lcd.setCursor(0, 1);
lcd.print("Distance: ");
lcd.print(distance);
lcd.print("cm ");
}
void displayPattern(int noteIndex) {
mx.clear();
for (int row = 0; row < 8; row++) {
for (int col = 0; col < 8; col++) {
if (notePatterns[noteIndex][row] & (1 << col)) {
mx.setPoint(row, col, true);
}
}
}
}
Step 3: Upload and Run
- Select the correct ESP32 Dev Module and Port.
- Upload the code.
- Open Serial Monitor at 115200 baud to check for initialization messages
- Move your hand in front of the ultrasonic sensor between 5cm and 40cm.
TIPS: Use any flat surface such as mobile phone book or any flat object for good detection waves from ultrasonic.
How It Works
Distance to Note Conversation
The core magic happens in the loop() function with this line:
int noteIndex = map(distance, minDistance, maxDistance, 0, 7);
map(): It takes the rawdistancevalue (e.g., 5cm to 40cm) and linearly scales it to the note index range (0 to 7).- Result:
- Hand at 5cm (closest) > Note Index 0 (C4)
- Hand at 40cm (farthest) > Note Index 7 (C5)
- Hand in the middle (22.5cm > Note Index 4 (G)
Audio and Visual Output
- Audio: The
playNote(noteIndex)function callstone(BUZZER_PIN, notes[noteIndex]), which generates the precise frequency (from thenotesarray) needed to play the musical tone. - Visual (Matrix): The
displayPattern(noteIndex)function reads the corresponding $8$-byte array fromnotePatterns(your custom shapes) and uses themx.setPoint()function to illuminate the LEDs on the 8×8 matrix, creating a new pattern for every note. - Visual (LCD): The
displayNote()function provides clear, human-readable feedback, showing the note name, its frequency, and the exact distance of your hand.
System Check and Troubleshooting
This guide is your fast-fix manual for when errors occur. It helps you quickly diagnose why something isn’t working, like a dark screen or a silent buzzer—and provides simple steps to correct the problem.
| Problem | Symptom/Observation | Solution |
| No Sound/Matrix Display | System initializes, but no notes play, or the matrix stays dark. | Check Libraries: Ensure LiquidCrystal_I2C.h and MD_MAX72XX.h are installed. |
| LCD is Blank | The screen is on, but shows no text. | Check I2C Address: Try changing 0x27 to 0x3F in the code. Double-check SDA (GPIO 21) and SCL (GPIO 22) wiring. |
| Matrix is Blank/Flickers | Matrix is unresponsive or shows garbage. | Check SPI Pins: Verify DIN (GPIO 23), CLK (GPIO 19), and CS (GPIO 15 or 4) are correctly wired. The order is critical. |
| Notes jump erratically | The sound changes rapidly even when the hand is still. | Signal Noise: Increase the delay(10) in loop() slightly. |
| Only one note plays | The system gets stuck on note 0 or note 7. | Check Distance Range: Verify the sensor is accurately reading between 5cm and 40cm. The distance boundaries might need fine-tuning in the code. |
Buy from:
Myduino AIoT Education Kit (click here) from myduino.com






